<!DOCTYPE html>
<html lang="en" style="height: 100%;">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>WebRTC 文件发送端</title>
</head>

<body style="height: 95%;font-size:12px;">
    <div style="height:100%;display:inline-block;width:60%;">
        <input type="file" id="fileInput" style="height: 4%;">
        <button id="sendButton">发送文件</button>
        <div style="height: 48%;overflow:auto">
            <div id="log_send"></div>
        </div>
        <hr>
        <div style="height:48%;overflow:auto">
            <div id="log_recv"></div>
        </div>
    </div>
    <div id="log" style="height: 100%;display:inline-block;width:38%;overflow:auto;border-left: 1px solid #ccc;"></div>
    <script>
        const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
        const host = window.location.host;
        const socket = new WebSocket(`${protocol}//${host}`);
        const fileInput = document.getElementById('fileInput');
        const sendButton = document.getElementById('sendButton');
        const log = document.getElementById('log');
        const log_send = document.getElementById('log_send');
        const log_recv = document.getElementById('log_recv');
        let start_time = Date.now();

        let peerConnection;
        let receivedChunks = [];
        let send_dataChannel;
        let fileMetadata;

        function speedOutput(chunkSize){
            speedOutput.chunkSize += chunkSize;
            if(Date.now()-start_time<1000) return speedOutput.speed_str;
            let speed = (speedOutput.chunkSize|0)/(Date.now()-start_time)/1024*1000
            let speed_str = speed.toFixed(1)+'kb/s'
            if (speed>=1024) {
                speed/=1024;
                speed_str = speed.toFixed(1)+'M/s'
            }
            if (speed>=1024) {
                speed/=1024;
                speed_str = speed.toFixed(1)+'G/s'
            }
            start_time= Date.now();
            speedOutput.chunkSize = 0;
            speedOutput.speed_str = "，速度 "+speed_str;
            return speedOutput.speed_str;
        }
        function logMessage(message,send=2,replace=false) {
            const p = document.createElement('p');
            p.textContent = message;
            switch(send){
            case 0:replace?log_send.replaceChild(p,log_send.lastChild):log_send.appendChild(p);break;
            case 1:replace?log_recv.replaceChild(p,log_recv.lastChild):log_recv.appendChild(p);break;
            case 2:log.appendChild(p);
            }
        }

        async function start() {
            sendButton.disabled=true;
            const pubStunList = [
                '192.168.5.22:3478',
                'thomas:3478'
            ]
            peerConnection = new RTCPeerConnection({iceServers:pubStunList.map(i => ({urls: 'stun:' + i}))});
            peerConnection.ondatachannel = (event) => {
                const dataChannel = event.channel;
                dataChannel.onmessage = (event) => {
                    if (typeof event.data === 'string') {
                        const data = JSON.parse(event.data);
                        if (data.type === 'file-metadata') {
                            fileMetadata = data;
                            logMessage(`开始接收文件: ${fileMetadata.name}`,1);
                        }
                    } else {
                        receivedChunks.push(event.data);
                        const receivedChunksCount = receivedChunks.length;
                        logMessage(`已接收块 ${receivedChunksCount} / ${fileMetadata.totalChunks} `+speedOutput(event.data.byteLength),1,true);
                        if (receivedChunksCount === fileMetadata.totalChunks) {
                            logMessage('文件接收完成，点击下面的链接下载文件',1);
                            const blob = new Blob(receivedChunks)
                            const fileUrl = URL.createObjectURL(blob)
                            const a = document.createElement('a')
                            a.href = fileUrl
                            a.textContent = fileMetadata.name
                            a.download = fileMetadata.name
                            a.style.display = 'inline-block'
                            log_recv.appendChild(a)
                        }
                    }
                };

                dataChannel.onopen = () => {
                    logMessage('数据接收通道已打开');
                };
                dataChannel.onerror = (error) => {
                    logMessage('数据接收通道错误: '+JSON.stringify(error));
                };
                dataChannel.onclose = () => {
                    logMessage('数据接收通道已关闭');
                };
            };

            send_dataChannel = peerConnection.createDataChannel('fileTransfer');
            send_dataChannel.onopen = () => {
                logMessage('数据发送通道已打开');
                sendButton.disabled = false;
            };
            send_dataChannel.onerror = (error) => {
                logMessage('数据发送通道错误: '+JSON.stringify(error));
            };
            send_dataChannel.onclose = () => {
                logMessage('数据发送通道已关闭');
            };

            peerConnection.onicecandidate = (event) => {
                if (event.candidate) {
                    //logMessage(event.candidate.address,true)
                    socket.send(JSON.stringify({
                        type: 'ice-candidate',
                        candidate: event.candidate
                    }));
                }
            };
            peerConnection.onicecandidateerror = (event) => {
                console.error('ICE candidate error:', event);
            };

            const offer = await peerConnection.createOffer();
            await peerConnection.setLocalDescription(offer);
            socket.send(JSON.stringify({
                type: 'offer',
                offer: offer
            }));
        }

        socket.onmessage = async (event) => {
            const data = JSON.parse(event.data);
            switch (data.type) {
                case 'offer':
                    const offer = new RTCSessionDescription(data.offer);
                    await peerConnection.setRemoteDescription(offer);
                    const answer = await peerConnection.createAnswer();
                    await peerConnection.setLocalDescription(answer);
                    socket.send(JSON.stringify({
                        type: 'answer',
                        answer: answer
                    }));
                    break;
                case 'answer':
                    peerConnection.setRemoteDescription(new RTCSessionDescription(data.answer));
                    break;
                case 'ice-candidate':
                    peerConnection.addIceCandidate(new RTCIceCandidate(data.candidate));
                    break;
            }
        };

        sendButton.addEventListener('click', async () => {
            const file = fileInput.files[0];
            if (file) {
                logMessage(`准备发送文件: ${file.name}`,0);
                const chunkSize = 60*1024; // 60k 每个块
                const maxSize = 1024*1024; // 1M 最大
                const totalChunks = Math.ceil(file.size / chunkSize);

                // 发送文件元数据
                send_dataChannel.send(JSON.stringify({
                    type: 'file-metadata',
                    name: file.name,
                    size: file.size,
                    totalChunks: totalChunks
                }));
                for (let i = 0; i < totalChunks; i++) {
                    const start = i * chunkSize;
                    const end = Math.min(start + chunkSize, file.size);
                    const chunk = file.slice(start, end);
                    send_dataChannel.send(chunk);
                    logMessage(`已发送块 ${i + 1} / ${totalChunks} `+speedOutput(chunkSize),0,true);
                    while (send_dataChannel.bufferedAmount > maxSize) {
                        await new Promise(resolve => send_dataChannel.onbufferedamountlow = resolve);
                    }
                }
                logMessage('文件发送完成',0);
            }
        });

        socket.onopen=start;
    </script>
</body>

</html>    